import os
import jpype
import h3

from ..data_structures.attribute_structures import Water, DemAttribute, Vegetation, Building, Geology, Soil
from ..data_structures.base_structures import Map, HexCell
from typing import List, Tuple
from myapp.pp_py.src.config.config import PPConfig, GlobalConfiguration
from ..utils.yaml_reader import load_yaml


class PPEngine:
    pp_engine_components = []

    @staticmethod
    def add_component(component: str):
        PPEngine.pp_engine_components.append(component)

    @staticmethod
    def pp(map: Map, start, end) -> List[Tuple[float, float]]:
        start_cell: HexCell = None
        end_cell: HexCell = None
        start_index = h3.geo_to_h3(start[0], start[1], GlobalConfiguration.h3_resolution)
        end_index = h3.geo_to_h3(end[0], end[1], GlobalConfiguration.h3_resolution)
        if start_index in map.cells:
            start_cell = map.cells[start_index]
        if end_index in map.cells:
            end_cell = map.cells[end_index]
        if start_cell is None or end_cell is None:
            # 清空禁行区
            PPConfig.noentryareas = set()
            # 清空模块
            PPEngine.pp_engine_components.clear()
            raise ValueError("起点或终点不在地图范围内")
        # 判断起终点是否可达
        if PPEngine.reject_cell(start_cell) or PPEngine.reject_cell(end_cell):
            # 清空禁行区
            PPConfig.noentryareas = set()
            # 清空模块
            PPEngine.pp_engine_components.clear()
            raise ValueError("起点或终点不可达")

        # 初始化变量
        open_set = set()  # 待评估的节点集合
        closed_set = set()  # 已评估的节点集合
        start_cell.g = 0  # 起点的g值
        for neighbor in start_cell.neighbors:
            if neighbor in map.cells:
                neighbor_cell = map.cells[neighbor]
                neighbor_cell.father = start_cell  # 设置父节点
                neighbor_cell.g = start_cell.g + h3.point_dist(start_cell.center, neighbor_cell.center)
                neighbor_cell.h = h3.point_dist(neighbor_cell.center, end_cell.center)
                neighbor_cell.f = neighbor_cell.g + neighbor_cell.h
                # h3_line是否被拒绝
                cells_line = h3.h3_line(neighbor_cell.index, start_cell.index)
                if PPEngine.reject_line(cells_line,map):
                    # 此轮循环舍弃这个neighbor_cell
                    continue
                open_set.add(neighbor_cell)
        closed_set.add(start_cell)
        current_cell: HexCell = None  # 当前节点
        # 开始A*算法
        while open_set:
            # 找到f值最小的节点
            current_cell = min(open_set, key=lambda c: c.f)
            # 刷新显示
            print(f"\r距离终点: {current_cell.h:.6f}", end='', flush=True)
            if current_cell == end_cell:
                break  # 找到终点，退出循环
            open_set.remove(current_cell)
            closed_set.add(current_cell)
            for neighbor in current_cell.neighbors:
                if neighbor in map.cells and map.cells[neighbor] not in closed_set:
                    neighbor_cell: HexCell = map.cells[neighbor]
                    # 计算g值的增量
                    neighbor_cell.g_increment = h3.point_dist(current_cell.center, neighbor_cell.center)
                    # g值更新
                    g = current_cell.g + neighbor_cell.g_increment
                    if neighbor_cell not in open_set or g < neighbor_cell.g:
                        neighbor_cell.g = g
                        neighbor_cell.h = h3.point_dist(neighbor_cell.center, end_cell.center)
                        neighbor_cell.f = 0.95 * neighbor_cell.g + neighbor_cell.h
                        neighbor_cell.father = current_cell  # 设置父节点
                        # cell是否被拒绝
                        if PPEngine.reject_line(h3.h3_line(neighbor_cell.index, current_cell.index),map):
                            # 此轮循环舍弃这个neighbor_cell
                            continue
                        open_set.add(neighbor_cell)
            if not open_set:
                print("\n未找到路径")
                # 清空禁行区
                PPConfig.noentryareas = set()
                # 清空模块
                PPEngine.pp_engine_components.clear()
                return None
        print() # 换行显示
        # 生成路径
        path_points = []
        while current_cell:
            path_points.append(current_cell.center)
            current_cell = current_cell.father
        path_points = path_points[::-1]  # 起点到终点
        path_points = list(dict.fromkeys(path_points))  # 去重

        # debug 输出
        # visualize_open_set(open_set)
        # 退出之前将cells中的cell父指针全部置为None
        for cell in map.cells.values():
            cell.father = None
        return path_points

    @staticmethod
    def reject_cell(neighbor_cell: HexCell) -> bool:
        if neighbor_cell.index in PPConfig.noentryareas:
            return True
        if 'modify_cell_with_water' in PPEngine.pp_engine_components:
            if PPEngine.reject_cell_by_water(neighbor_cell):
                return True
        if 'modify_cell_with_vegetation' in PPEngine.pp_engine_components:
            if PPEngine.reject_cell_by_vegetation(neighbor_cell):
                return True
        if 'modify_cell_with_building' in PPEngine.pp_engine_components:
            if PPEngine.reject_cell_by_building(neighbor_cell):
                return True
        if 'modify_cell_with_geology' in PPEngine.pp_engine_components:
            if PPEngine.reject_cell_by_geology(neighbor_cell):
                return True
        if 'modify_cell_with_soil' in PPEngine.pp_engine_components:
            if PPEngine.reject_cell_by_soil(neighbor_cell):
                return True
        if 'modify_cell_with_slope' in PPEngine.pp_engine_components:
            if PPEngine.reject_cell_by_slope(neighbor_cell):
                return True
        return False

    @staticmethod
    def reject_cell_by_water(neighbor_cell: HexCell) -> bool:
        if PPConfig.has_config('Water.Depth'):
            if not neighbor_cell.has_attribute(Water):
                return False
            elif neighbor_cell.get_attribute(Water).get_sub_attribute(Water.Depth).get_value() > PPConfig.get_config('Water.Depth'):
                return True
        return False

    @staticmethod
    def reject_cell_by_elevation(neighbor_cell: HexCell) -> bool:
        if PPConfig.has_config('DemAttribute.Elevation'):
            if not neighbor_cell.has_attribute(DemAttribute.Elevation):
                return False
            elif neighbor_cell.get_attribute(DemAttribute.Elevation).get_value() > PPConfig.get_config('DemAttribute.Elevation'):
                return True
        return False

    @staticmethod
    def reject_cell_by_relief(neighbor_cell: HexCell) -> bool:
        if PPConfig.has_config('DemAttribute.Relief'):
            if not neighbor_cell.has_attribute(DemAttribute.Relief):
                return False
            elif neighbor_cell.get_attribute(DemAttribute.Relief).get_value() > PPConfig.get_config('DemAttribute.Relief'):
                return True
        return False

    @staticmethod
    def reject_cell_by_slope(neighbor_cell: HexCell) -> bool:
        if PPConfig.has_config('DemAttribute.Slope'):
            if not neighbor_cell.has_attribute(DemAttribute.Slope):
                return False
            elif neighbor_cell.get_attribute(DemAttribute.Slope).get_value() > PPConfig.get_config('DemAttribute.Slope'):
                return True
        return False

    @staticmethod
    def reject_cell_by_vegetation(neighbor_cell: HexCell) -> bool:
        """
            考虑植被对 cell 通行能力的影响
            输入参数：
                - VehicleWidth (车辆宽度 b)
                - Vegetation.AverageHeight (植被高度阈值)
            规则：
                - 植被类型为 grass / crop / shrub 时默认可通过
                - 其他植被类型时，如果植被间距 Spacing <= b+1，则拒绝通过 (S = 1 / sqrt(D))
                - 植被高度超过配置阈值时，拒绝通过
        """
        # 植被宽度间距阈值（依赖 VehicleWidth）
        if PPConfig.has_config('VehicleWidth'):
            if not neighbor_cell.has_attribute(Vegetation):
                return False
            else:
                veg_attr = neighbor_cell.get_attribute(Vegetation)
                veg_type = veg_attr.get_sub_attribute(Vegetation.Type).get_value()
                # 非草/农作物/灌木时才需要计算株距
                if veg_type not in {"grass", "crop", "shrub"}:
                    density = veg_attr.get_sub_attribute(Vegetation.Density)
                    if density is not None:
                        density = veg_attr.get_sub_attribute(Vegetation.Density).get_value()
                        if density > 0:  # 防止除零
                            spacing = 1 / (density ** 0.5)
                            if spacing <= PPConfig.get_config('VehicleWidth') + 1:
                                return True
        # 植被高度阈值
        if PPConfig.has_config('Vegetation.AverageHeight'):
            if not neighbor_cell.has_attribute(Vegetation):
                return False
            elif neighbor_cell.get_attribute(Vegetation).get_sub_attribute(Vegetation.AverageHeight).get_value() > \
                    PPConfig.get_config('Vegetation.AverageHeight'):
                # 植被高度超过阈值
                return True
        # 最后考虑没有阈值但仍然有植被的情况
        if not neighbor_cell.has_attribute(Vegetation):
            return False
        else:
            # 默认规则：草、农作物、灌木允许，其余拒绝
            veg_type = neighbor_cell.get_attribute(Vegetation).get_sub_attribute(Vegetation.Type).get_value()
            if veg_type not in {"grass", "crop", "shrub"}:
                return True
        return False

    @staticmethod
    def reject_cell_by_building(neighbor_cell: HexCell) -> bool:
        if not neighbor_cell.has_attribute(Building):
            return False
        else:
            return True
        # 获取车辆质量
        vehicle_mass = PPConfig.get_config("Vehicle.Mass")
        if vehicle_mass is None:
            raise Exception("请配置车辆重量")
        # 获取车辆速度
        vehicle_velocity = PPConfig.get_config("Vehicle.Speed")
        if vehicle_velocity is None:
            raise Exception("请配置车辆速度")
        # 获取墙体强度等级
        building_hardness = neighbor_cell.get_attribute(Building).get_sub_attribute(Building.Hardness)
        if building_hardness is None:
            raise Exception("网格的建筑物属性类中的墙体强度等级数据为空")
        hardness = building_hardness.get_value()
        # 根据强度等级计算系数
        if hardness <= 1:
            c_factor = PPConfig.get_config("BuildingHardness.factor1")
        elif 1 < hardness <= 2:
            c_factor = PPConfig.get_config("BuildingHardness.factor2")
        elif 2 < hardness <= 3:
            c_factor = PPConfig.get_config("BuildingHardness.factor3")
        else:
            c_factor = PPConfig.get_config("BuildingHardness.factor4")
        # K 常数（源码里写死，后续可拓展）
        k_constant = PPConfig.get_config("BuildingDestroyConstant")
        # 计算建筑物被破坏阈值 DT
        dt = c_factor * k_constant
        # 计算车辆冲击动能 ET
        et = 0.5 * vehicle_mass * (vehicle_velocity ** 2)
        # 判断是否达到阈值
        if et <= dt:
            # 建筑物不能被破坏，无法通过
            return True
        return False

    @staticmethod
    def reject_cell_by_geology(neighbor_cell: HexCell) -> bool:
        if PPConfig.has_config('Geology.Wrinkle'):
            if not neighbor_cell.has_attribute(Geology):
                return False
            elif neighbor_cell.get_attribute(Geology).get_sub_attribute(
                    Geology.Wrinkle).get_value() > PPConfig.get_config('Geology.Wrinkle'):
                return True
        # 断层：只要有断层属性，直接不可通行
        if neighbor_cell.has_attribute(Geology):
            fault_attr = neighbor_cell.get_attribute(Geology).get_sub_attribute(Geology.Fault)
            if fault_attr is not None:
                return True
        return False

    @staticmethod
    def reject_cell_by_soil(neighbor_cell: HexCell) -> bool:
        if not neighbor_cell.has_attribute(Soil):
            return False
        vehicle_weight = PPConfig.get_config("VehicleWeight")  # kg
        contact_area = PPConfig.get_config("VehicleContactArea")  # m²
        soil_attr = neighbor_cell.get_attribute(Soil)
        hardness_attr = soil_attr.get_sub_attribute(Soil.Hardness)
        if not hardness_attr:
            return False
        ci = hardness_attr.get_value()  # kPa
        # 计算车辆接地压力和土壤承载力
        vgp = (vehicle_weight * 9.8) / contact_area / 1000  # kPa
        # 不可通行条件
        if vgp > ci:
            return True
        return False

    @staticmethod
    def reject_line(cells_line: List[str], map: Map) -> bool:
        for cell_index in cells_line:
            cell = map.cells[cell_index]
            if PPEngine.reject_cell(cell):
                return True
        return False

    @staticmethod
    def pp_by_road(map: Map, start, end) -> List[Tuple[float, float]]:
        route_class = jpype.JClass("com.graphhoper.Route")
        # 拼接osm文件路径
        current_file_dir = os.path.dirname(os.path.abspath(__file__))
        osm_path_relative = os.path.join(current_file_dir, '..', '..','..','..','data','jiangsu-latest.osm.pbf')
        osm_path_abs = os.path.abspath(osm_path_relative)
        result = route_class.route(start[0], start[1], end[0], end[1], osm_path_abs)
        path2 = []
        for point in result:
            path2.append((point.lat, point.lng))
        return path2
        start_road = path2[0]
        end_road = path2[-1]
        path1 = PPEngine.pp(map, start, start_road)
        path3 = PPEngine.pp(map, end_road, end)
        path = path1 + path2 + path3
        return path
